#include "filewidget.h"

#include <QtGui>
#include <iostream>

#include <QHeaderView>
#include <QItemSelectionModel>
#include <QStandardItemModel>
#include <QTableView>
#include <QtXml/QDomDocument>
#include <QtXml/QDomElement>
#include <QtXml/QDomNode>
#include <QtCore/qmath.h>

#include "delegate.h"


fileWidget::fileWidget(QWidget *parent) : QWidget(parent)
{
    fileName = "";
    fileToCompareName = "";
    offset = 0;
    offsetToCompare = 0;
    layoutToCompare = new QGridLayout();
    layout = new QGridLayout();
    verticalLayout = new QVBoxLayout(this);
    setLayout(verticalLayout);
    verticalLayout->addLayout(layout);
    verticalLayout->addLayout(layoutToCompare);
    this->parent = parent;
    byteOrder = QDataStream::LittleEndian;
}

fileWidget::~fileWidget(){
}

bool fileWidget::open(){
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open file"), "/");
    if(!(fileName.isEmpty())){
        this->fileName = fileName;
        return loadFile(fileName);
    }
    return false;
}

bool fileWidget::openFileToCompare(){
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open file"), "/");
    if(!(fileName.isEmpty())){
        this->fileToCompareName = fileName;
        return loadFileToCompare(fileName);
    }
    return false;
}

//wczytanie pliku
bool fileWidget::loadFile(const QString &fileName){
    QString line;
    quint8 byte;

    //layout = new QGridLayout(this);
    //verticalLayout->addLayout(layout);

    QFile inputFile(fileName);
    inputFile.open(QIODevice::ReadOnly);
    QDataStream in(&inputFile);

    while(!in.atEnd()){
        in >> byte;
        line.push_back(byte);
    }
    inputFile.close();

    textEdit = new QTextEdit(this);
    //layout->addWidget(textEdit);

    textEdit->setPlainText(line);
    QTextCursor cursor = textEdit->textCursor();
    cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor, 1);
    textEdit->setEnabled(false);

    // data from QString are transformed to QByteArray
    array = line.toAscii().toHex();
    //std::cout<<"array size: "<<array.size()<<std::endl;
    // data from QByteArray are transformed to QString (will be visible as hex values)
    line=array.data();

    int maxRow = inputFile.size()/16+1;
    model = new QStandardItemModel(maxRow, 16, this);
    tableView = new QTableView(this);
    tableView->setModel(model);
    tableView->setShowGrid(false);
    QHeaderView *horizontalHeaderView = tableView->horizontalHeader();
    horizontalHeaderView->setMinimumSectionSize(35);
    horizontalHeaderView->resizeSections(QHeaderView::ResizeToContents);
    //horizontalHeaderView->setDefaultSectionSize(10);
    QHeaderView *verticalHeaderView = tableView->verticalHeader();
    verticalHeaderView->resizeSections(QHeaderView::ResizeToContents);
    //verticalHeaderView->setDefaultSectionSize(10);

    lineEdit = new delegate(this);
    tableView->setItemDelegate(lineEdit);

    QFile inputFile2(fileName);

    inputFile2.open(QIODevice::ReadOnly);
    QDataStream in2(&inputFile2);
    if(in2.atEnd()){
        //std::cout<<"end of in2"<<std::endl;
    }

    //wypełnianie kolejnych pól obiektu klasy QStandardItemModel przeformatowanymi danymi z wczytanego pliku
    for (int row = 0; row <= maxRow; ++row) {
             for (int column = 0; column < 16; ++column) {
                 QModelIndex index = model->index(row, column, QModelIndex());
                 if(in2.atEnd())
                     break;
                 in2 >> byte;
                 model->setData(index, Qt::AlignCenter, Qt::TextAlignmentRole);
                 model->setData(index,QString(QString(byte).toAscii().toHex().data()));
             }
    }
    layout->addWidget(tableView);
    layout->addWidget(tableView,0,0);

    connect(tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(setOffset(QModelIndex)));

    /*button = new QPushButton(tr("Load XML template"));
    layout->addWidget(button,0,2);
    connect(button, SIGNAL(clicked()), this, SLOT(openXML()));

    label = new QLabel();
    //label->setNum(0);
    layout->addWidget(label,0,3);*/

    connect(model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(setMainWindowModified(QStandardItem *)));

    inputFile2.close();
    return true;
}

bool fileWidget::loadFileToCompare(const QString &fileToCompareName)
{
    QString line;
    quint8 byte;

    //layoutToCompare = new QGridLayout(this);
    //verticalLayout->addLayout(layoutToCompare);

    QFile inputFile(fileToCompareName);
    inputFile.open(QIODevice::ReadOnly);
    QDataStream in(&inputFile);

    while(!in.atEnd()){
        in >> byte;
        line.push_back(byte);
    }
    inputFile.close();

    // data from QString are transformed to QByteArray
    array = line.toAscii().toHex();
    // data from QByteArray are transformed to QString (will be visible as hex values)
    line=array.data();

    int maxRow = inputFile.size()/16+1;
    modelToCompare = new QStandardItemModel(maxRow, 16, this);
    tableViewToCompare = new QTableView(this);
    tableViewToCompare->setModel(modelToCompare);
    tableViewToCompare->setShowGrid(false);
    QHeaderView *horizontalHeaderView = tableViewToCompare->horizontalHeader();
    horizontalHeaderView->setMinimumSectionSize(35);
    horizontalHeaderView->resizeSections(QHeaderView::ResizeToContents);
    //horizontalHeaderView->setDefaultSectionSize(10);
    QHeaderView *verticalHeaderView = tableViewToCompare->verticalHeader();
    verticalHeaderView->resizeSections(QHeaderView::ResizeToContents);
    //verticalHeaderView->setDefaultSectionSize(10);

    lineEditToCompare = new delegate(this);
    tableViewToCompare->setItemDelegate(lineEditToCompare);

    QFile inputFile2(fileToCompareName);

    inputFile2.open(QIODevice::ReadOnly);
    QDataStream in2(&inputFile2);
    if(in2.atEnd()){
        //std::cout<<"end of in2"<<std::endl;
    }
    for (int row = 0; row <= maxRow; ++row) {
             for (int column = 0; column < 16; ++column) {
                 QModelIndex index = modelToCompare->index(row, column, QModelIndex());
                 if(in2.atEnd())
                     break;
                 in2 >> byte;
                 modelToCompare->setData(index, Qt::AlignCenter, Qt::TextAlignmentRole);
                 modelToCompare->setData(index,QString(QString(byte).toAscii().toHex().data()));
             }
    }

    layoutToCompare->addWidget(tableViewToCompare);
    layoutToCompare->addWidget(tableViewToCompare,0,0);
    connect(tableViewToCompare, SIGNAL(clicked(QModelIndex)), this, SLOT(setOffsetToCompare(QModelIndex)));

    /*button = new QPushButton(tr("Load XML template"));
    layout->addWidget(button,0,2);
    connect(button, SIGNAL(clicked()), this, SLOT(openXML()));

    label = new QLabel();
    label->setNum(0);
    layout->addWidget(label,0,3);*/

    inputFile2.close();
    return true;
}

void fileWidget::openXML(){
    XMLFileName = QFileDialog::getOpenFileName(this, tr("Open XML file"), "/");
    if(!(fileName.isEmpty()) && !(XMLFileName.isEmpty())){
       createWidgetFromTemplate(XMLFileName);
    }
}

//wyświetlenie zawartości pliku sformatowanego przez szablon
void fileWidget::createWidgetFromTemplate(const QString &XMLFileName){

    // wgranie zawartości pliku XML do dokumentu DOM-XML
    QFile file(XMLFileName);
    //doc = new QDomDocument("mydocument");
    if (!file.open(QIODevice::ReadOnly))
        return;
    if (!doc.setContent(&file)){
        file.close();
        return;
    }
    file.close();

    // Na podstwie pliku xml zlicz ile wierszy powinna mieć tabela (numberOfElements)
    // print out the element names of all elements that are direct children
    // of the outermost element.
    QDomElement docElem = doc.documentElement();
    int numberOfElements = 0;
    QDomNode n = docElem.firstChild();
    while(!n.isNull()){
        QDomElement e = n.toElement(); // try to convert the node to an element.
        if(!e.isNull()){
            numberOfElements++;
            //std::cout << numberOfElements <<" "<< qPrintable(e.attribute("opis")) <<" "<< qPrintable(e.text()) << std::endl; // the node really is an element.
        }
        n = n.nextSibling();
    }

    // Tworzenie modelu i widoku tabeli
    TemplateModel = new QStandardItemModel(numberOfElements, 2, this);
    TemplateTableView = new QTableView(this);
    TemplateTableView->setModel(TemplateModel);

    templateDelegate = new delegateTemplate(&doc,model,offset,&byteOrder,this);
    TemplateTableView->setItemDelegate(templateDelegate);

    // Dodawanie do tabeli nazw danych pobieranych z pliku xml
    QDomNode m = docElem.firstChild();
    if(m.isNull())
        return;
    for (int row = 0; row < numberOfElements; ++row) {
             for (int column = 0; column < 2; ++column) {
                 QModelIndex index = TemplateModel->index(row, column, QModelIndex());
                 if( column == 0 ){
                     QDomElement f = m.toElement();
                     if( f.isNull()){
                         //std::cout<<"f is null"<<std::endl;
                     }else{
                         //std::cout<<qPrintable(f.text())<<std::endl;
                         TemplateModel->setData(index, QString(qPrintable(f.attribute("description"))));
                         m = m.nextSibling();
                     }
                 }
                 //std::cout<<"row: "<<row<<"; column: "<<column<<std::endl;

             }
    }

    // Dodawanie do tabeli danych z pliku
    m = docElem.firstChild();
    if(m.isNull()){
        //std::cout<<"m is null"<<std::endl;
        return;
    }
    QString line;
    quint8 unsigned_byte;
    qint8 signed_byte;
    quint16 unsigned_16bit_integer;
    qint16 signed_16bit_integer;
    quint32 unsigned_32bit_integer;
    qint32 signed_32bit_integer;
    quint64 unsigned_64bit_integer;
    qint64 signed_64bit_integer;

    /*QFile inputFile(fileName);
    inputFile.open(QIODevice::ReadOnly);
    QDataStream in(&inputFile);
    in.setByteOrder(byteOrder);*/

    //odczytanie danych z modelu reprezentującego zawartość pliku
    int maxRow = model->rowCount(QModelIndex());
    QString dataFromTextEditHex = "";
    for(int row = 0; row < maxRow; ++row){
        for(int column = 0; column < 16; ++column){
            QModelIndex index = model->index(row, column, QModelIndex());
            dataFromTextEditHex += model->data(index,Qt::DisplayRole).toString();
        }
    }
    //utworzenie tablicy bajtów, reprezentującą plik
    QByteArray byteArray;
    byteArray = dataFromTextEditHex.toAscii();
    byteArray = QByteArray::fromHex(byteArray);
    //utworzenie strumienia na podstawie tablicy bajtów
    QDataStream in(byteArray);
    in.setByteOrder(byteOrder);
    //uwzględnienie offsetu przed pobieraniem danych
    for(int i=1;i<=offset;++i)
        in >> signed_byte;

    //pobranie danych z pliku do tabelki
    for( int row = 0; row < numberOfElements; ++row){
        for( int column = 0; column < 2; ++column){
            QModelIndex index = TemplateModel->index(row, column, QModelIndex());
            if(column == 1){
                QDomElement f = m.toElement();
                if(f.isNull()){
                    //std::cout<<"f is null (2)"<<std::endl;
                }
                else{
                    if(f.text()=="int8"){
                        in >> signed_byte;
                        TemplateModel->setData(index, signed_byte);
                    }
                    else if(f.text()=="uint8"){
                        in >> unsigned_byte;
                        TemplateModel->setData(index, unsigned_byte);
                    }
                    else if(f.text()=="int16"){
                        in >> signed_16bit_integer;
                        TemplateModel->setData(index, signed_16bit_integer);
                    }
                    else if(f.text()=="uint16"){
                        in >> unsigned_16bit_integer;
                        TemplateModel->setData(index, unsigned_16bit_integer);
                    }
                    else if(f.text()=="int32"){
                       in >> signed_32bit_integer;
                       TemplateModel->setData(index, signed_32bit_integer);
                    }
                    else if(f.text()=="uint32"){
                       in >> unsigned_32bit_integer;
                       TemplateModel->setData(index, unsigned_32bit_integer);
                    }
                    else if(f.text()=="int64"){
                       in >> signed_64bit_integer;
                       TemplateModel->setData(index, signed_64bit_integer);
                    }
                    else if(f.text()=="uint64"){
                       in >> unsigned_64bit_integer;
                       TemplateModel->setData(index, unsigned_64bit_integer);
                    }
                    else if(f.text()=="char"){
                        for(int i=0; i<f.attribute("size").toInt(); ++i){
                            in >> unsigned_byte;
                            line.push_back(unsigned_byte);
                        }
                        TemplateModel->setData(index, line);
                        line.clear();
                    }
                    else{}
                    m = m.nextSibling();
                }
            }
        }
    }
    //inputFile.close();
    layout->addWidget(TemplateTableView,0,5);

    buttonGroup = new QButtonGroup(this);
    radioButtonL = new QRadioButton("Little Endian", this);

    radioButtonB = new QRadioButton("Big Endian", this);
    if(byteOrder == QDataStream::LittleEndian){
        radioButtonL->setChecked(true);
    }else{
        radioButtonB->setChecked(true);
    }
    buttonGroup->addButton(radioButtonL);
    buttonGroup->addButton(radioButtonB);
    layout->addWidget(radioButtonL,1,5);
    layout->addWidget(radioButtonB,2,5);

    connect(buttonGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(setByteOrder(QAbstractButton*)));
}

bool fileWidget::save(){
    if(fileName.isEmpty()){
        return saveAs();
    }
    else
        return saveFile();
}

bool fileWidget::saveAs(){
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save as ..."), "/");
    if(!(fileName.isEmpty())){
        this->fileName = fileName;
        return saveFile();
    }
    return false;
}

bool fileWidget::saveFile(){
    QFile outputFile(fileName);
    if(!outputFile.open(QIODevice::WriteOnly)){
        return false;
    }
    else{
        QDataStream out(&outputFile);
        int maxRow = model->rowCount(QModelIndex());

        //odczytanie danych z modelu do obiektu typu QString
        QString dataFromTextEditHex = "";
        for (int row = 0; row < maxRow; ++row) {
            for (int column = 0; column < 16; ++column) {
                QModelIndex index = model->index(row, column, QModelIndex());
                dataFromTextEditHex += model->data(index,Qt::DisplayRole).toString();
            }
        }

        //zamiana wartości heksadecymalnych na binarne
        array = dataFromTextEditHex.toAscii();
        array = QByteArray::fromHex(array);

        //zapisywanie kolejnych bajtów danych
        quint8 byte;
        for(int i=0; i<array.size(); ++i){
            byte = array[i];
            out << byte;
        }

    }
    parent->setWindowModified(false);
    return true;
}

void fileWidget::updateTemplateWidget(){
    if(model != NULL){

        QString line;
        quint8 unsigned_byte;
        qint8 signed_byte;
        quint16 unsigned_16bit_integer;
        qint16 signed_16bit_integer;
        quint32 unsigned_32bit_integer;
        qint32 signed_32bit_integer;
        quint64 unsigned_64bit_integer;
        qint64 signed_64bit_integer;

        int maxRow = model->rowCount(QModelIndex());
        QString dataFromTextEditHex = "";
        for(int row = 0; row < maxRow; ++row){
            for(int column = 0; column < 16; ++column){
                QModelIndex index = model->index(row, column, QModelIndex());
                dataFromTextEditHex += model->data(index,Qt::DisplayRole).toString();
            }
        }

        QByteArray byteArray;
        byteArray = dataFromTextEditHex.toAscii();
        byteArray = QByteArray::fromHex(byteArray);

        QDataStream in(byteArray);
        in.setByteOrder(byteOrder);
        for(int i=1;i<=offset;++i)
            in >> signed_byte;

        QDomElement docElem = doc.documentElement();

        QDomNode m = docElem.firstChild();
        if(m.isNull()){
            //std::cout<<"m is null"<<std::endl;
            return;
        }


            //std::cout<<TemplateModel->rowCount(QModelIndex())<<std::endl;
            for( int row = 0; row < TemplateModel->rowCount(QModelIndex()); ++row){
                for( int column = 0; column < 2; ++column){
                    QModelIndex index = TemplateModel->index(row, column, QModelIndex());
                    if(column == 1){
                        QDomElement f = m.toElement();
                        //std::cout<<qPrintable(f.attribute("opis"))<<" + "<<qPrintable(f.text())<<std::endl;

                        if(f.isNull()){
                            //std::cout<<"f is null (2)"<<std::endl;
                        }
                        else{
                            if(f.text()=="int8"){
                                in >> signed_byte;
                                TemplateModel->setData(index, signed_byte);
                            }
                            else if(f.text()=="uint8"){
                                in >> unsigned_byte;
                                TemplateModel->setData(index, unsigned_byte);
                            }
                            else if(f.text()=="int16"){
                                in >> signed_16bit_integer;
                                TemplateModel->setData(index, signed_16bit_integer);
                            }
                            else if(f.text()=="uint16"){
                                in >> unsigned_16bit_integer;
                                TemplateModel->setData(index, unsigned_16bit_integer);
                            }
                            else if(f.text()=="int32"){
                               in >> signed_32bit_integer;
                               TemplateModel->setData(index, signed_32bit_integer);
                            }
                            else if(f.text()=="uint32"){
                               in >> unsigned_32bit_integer;
                               TemplateModel->setData(index, unsigned_32bit_integer);
                            }
                            else if(f.text()=="int64"){
                               in >> signed_64bit_integer;
                               TemplateModel->setData(index, signed_64bit_integer);
                            }
                            else if(f.text()=="uint64"){
                               in >> unsigned_64bit_integer;
                               TemplateModel->setData(index, unsigned_64bit_integer);
                            }
                            else if(f.text()=="char"){
                                for(int i=0; i<f.attribute("size").toInt(); ++i){
                                    in >> unsigned_byte;
                                    line.push_back(unsigned_byte);
                                }
                                TemplateModel->setData(index, line);
                                line.clear();
                            }
                            else{}
                            m = m.nextSibling();
                        }
                    }
                }
            }
    }
}

void fileWidget::compare(){
    if( model != NULL && modelToCompare != NULL)
    {
        QByteArray modelArray;
        QByteArray modelToCompareArray;
        QString dataString = "";
        int maxRow = model->rowCount(QModelIndex());
        for(int row = 0; row <maxRow; ++row){
            for(int column = 0; column < 16; ++column){
                QModelIndex index = model->index(row, column, QModelIndex());
                dataString += model->data(index,Qt::DisplayRole).toString();
            }
        }
        modelArray = dataString.toAscii();
        modelArray = QByteArray::fromHex(modelArray);
        modelArray.remove(0, offset);

        maxRow = modelToCompare->rowCount(QModelIndex());
        dataString = "";
        for(int row = 0; row <maxRow; ++row){
            for(int column = 0; column < 16; ++column){
                QModelIndex index = modelToCompare->index(row, column, QModelIndex());
                dataString += modelToCompare->data(index,Qt::DisplayRole).toString();
            }
        }
        modelToCompareArray = dataString.toAscii();
        modelToCompareArray = QByteArray::fromHex(modelToCompareArray);
        modelToCompareArray.remove(0, offsetToCompare);

        quint8 byte;
        quint8 byte1;
        unsigned int i;
        //std::cout<<"modelArray.size(): "<<modelArray.size()<<std::endl;
        //std::cout<<"modelArray.size(): "<<modelToCompareArray.size()<<std::endl;
        for(i = 0; i<modelArray.size() && i<modelToCompareArray.size(); ++i){
            byte = modelArray[i];
            byte1 = modelToCompareArray[i];
            if( byte != byte1){
                //std::cout<<"znalazlem niepasujacy bajt: "<<byte<<" != "<<byte1<<std::endl;
                break;
            }
        }


        model->setData(indexToCompare, QColor(Qt::white), Qt::BackgroundColorRole);
        modelToCompare->setData(indexToCompare2, QColor(Qt::white), Qt::BackgroundColorRole);

        // offset + i   -   indeks pierwszego rozniacego sie bajtu
        //std::cout<<"pierwszy różniący się bajt w oryginalnym pliku: "<<offset+i<<std::endl;
        QModelIndex index = model->index(qFloor((offset+i)/16), (offset+i)%16, QModelIndex());
        model->setData(index, QColor(Qt::red), Qt::BackgroundColorRole);
        tableView->scrollTo(index, QAbstractItemView::PositionAtCenter);
        indexToCompare = index;
        // offsetToCompare + i  -   j.w.
        //std::cout<<"pierwszy różniący się bajt w porównywanym pliku to pliku: "<<offsetToCompare+i<<std::endl;
        index = modelToCompare->index(qFloor((offsetToCompare+i)/16), (offsetToCompare+i)%16, QModelIndex());
        modelToCompare->setData(index, QColor(Qt::red), Qt::BackgroundColorRole);
        tableViewToCompare->scrollTo(index, QAbstractItemView::PositionAtCenter);
        indexToCompare2 = index;
    }

}

void fileWidget::setByteOrder(QAbstractButton* button){
    QRadioButton *radioButton = static_cast<QRadioButton *>(button);
    if(radioButton->text() == QString("Big Endian")){
        byteOrder = QDataStream::BigEndian;
        //std::cout<<"set big endian"<<std::endl;
        updateTemplateWidget();
    }
    else if(radioButton->text() == QString("Little Endian")){
        byteOrder = QDataStream::LittleEndian;
        //std::cout<<"set little endian"<<std::endl;
        updateTemplateWidget();
    }


}

void fileWidget::setOffset(QModelIndex index){
    //std::cout<<"row: "<<index.row()<<" column: "<<index.column();
    offset = index.column()+index.row()*16;
    QString napis;
    napis.setNum(offset);
    //std::cout<<" offset: "<<qPrintable(napis)<<std::endl;
    //label->setText(napis);
}

void fileWidget::setOffsetToCompare(QModelIndex index){
    //std::cout<<"row: "<<index.row()<<" column: "<<index.column();
    offsetToCompare = index.column()+index.row()*16;
    QString napis;
    napis.setNum(offsetToCompare);
    //std::cout<<" offsetToCompare: "<<qPrintable(napis)<<std::endl;
}

void fileWidget::setMainWindowModified(QStandardItem *){
    parent->setWindowModified(true);
}

void fileWidget::closeEvent(QCloseEvent *event){
    event->accept();
}
